package com.yc.spring.demo;

import com.yc.spring.aop.AopConfig;
import com.yc.spring.demo.MyAnnotationConfigApplicationContext;
import com.yc.spring.demo.biz.IUserBiz;
import com.yc.spring.demo.biz.UserBiz;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.SourceLocation;

import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Company 源辰信息
 *
 * @author navy
 * @date 2024/8/3
 * Email haijunzhou@hnit.edu.cn
 */
public class AopApplicationContext extends MyAnnotationConfigApplicationContext {

    List<AspectInfo> aspectInfoList = new ArrayList<>();

    Map<String,Object> targetBeanMap = new HashMap<>();
    Map<String,Object> proxyBeanMap = new HashMap<>();

    public AopApplicationContext(Class configuratioin) throws UnsupportedEncodingException, ClassNotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException {
        super(configuratioin);
        // 提取切点+增强
        beanDefinitionMap.forEach((name,beanDefinition)->{
            Object beanClass = beanDefinition.getBeanClass();
            Class cls = (Class) beanClass;
            Annotation aspect = cls.getAnnotation(Aspect.class);
            if(aspect!=null){
                // 找到对应的bean 不考虑懒加载
                Object aspectBean = singletonObjects.get(name);
                // 提取所有的增强方法
                for (Method declaredMethod : cls.getDeclaredMethods()) {
                    Before before = declaredMethod.getAnnotation(Before.class);
                    AfterReturning afterReturning = declaredMethod.getAnnotation(AfterReturning.class);
                    if(before!=null){
                        String pointcut = before.value();
                        // TODO  如何提取切点表达式
                        if(pointcut.matches("\\w+\\(\\s*\\)")){
                            // 引入切点方法
                            String pointcutMethodName = pointcut.replaceAll("(\\w+)\\(\\s*\\)", "$1");
                            try {
                                Method pointcutMethod = cls.getDeclaredMethod(pointcutMethodName);
                                Pointcut pointcutAnno = pointcutMethod.getAnnotation(Pointcut.class);
                                pointcut = pointcutAnno.value();
                            } catch (NoSuchMethodException e) {
                                throw new RuntimeException(e);
                            }
                        }
                        AspectInfo ai = new AspectInfo("before", aspectBean, declaredMethod, pointcut);
                        aspectInfoList.add(ai);
                    }
                    if(afterReturning!=null){
                        String pointcut = afterReturning.value();
                        // TODO  如何提取切点表达式
                        if(pointcut.matches("\\w+\\(\\s*\\)")){
                            // 引入切点方法
                            String pointcutMethodName = pointcut.replaceAll("(\\w+)\\(\\s*\\)", "$1");
                            try {
                                Method pointcutMethod = cls.getDeclaredMethod(pointcutMethodName);
                                Pointcut pointcutAnno = pointcutMethod.getAnnotation(Pointcut.class);
                                pointcut = pointcutAnno.value();
                            } catch (NoSuchMethodException e) {
                                throw new RuntimeException(e);
                            }
                        }
                        AspectInfo ai = new AspectInfo("afterReturning", aspectBean, declaredMethod, pointcut);
                        aspectInfoList.add(ai);
                    }
                }
            }
        });

        System.out.println("-------------- 切面对象 ---------------");
        aspectInfoList.forEach(System.out::println);

        // 扫描所有的 bean, 用切点切方法, 收集被切到的bean (忽略懒加载)
        singletonObjects.forEach((name,bean)->{
            for (Method declaredMethod : bean.getClass().getDeclaredMethods()) {
                // 使用切点判断该方法是否被切
                for (AspectInfo aspectInfo : aspectInfoList) {
                    //                    aspectInfo.pointcut; // execution(* com.yc.spring.demo..*.find*(..));
                    String regExp = aspectInfo.pointcut.replaceAll(
                            "execution\\((.+)\\)", "$1");
                    regExp = regExp.replaceAll("(\\.{2}|\\*)",".*");
                    if(declaredMethod.toString().matches(regExp)){
                        targetBeanMap.put(name,bean);
                        return;
                    }
                }
            }
        });
        System.out.println("-------------- 目标对象 ---------------");
        targetBeanMap.values().forEach(System.out::println);

        // 生成代理对象
        for (Map.Entry<String, Object> entry : targetBeanMap.entrySet()) {
            String name = entry.getKey();
            Object bean = entry.getValue();
            // JDK or CGLIB
            if (bean.getClass().getInterfaces().length == 0) {
                // TODO CGLIB
            } else {
                // JDK
                Object proxyBean = Proxy.newProxyInstance(
                        bean.getClass().getClassLoader(),
                        bean.getClass().getInterfaces(),
                        (proxy,method,args)->{
                            try{
                                // 前置增强
                                List<AspectInfo> befores = getAdvice("before", method);
                                JoinPoint joinPoint = createJoinPoint(method, args, bean);
                                for (AspectInfo aspectInfo : befores) {
                                    aspectInfo.adviceMethod.invoke(aspectInfo.aspectBean,joinPoint);
                                }
                                Object ret = method.invoke(bean, args);
                                // 返回增强
                                List<AspectInfo> afterReturnings = getAdvice("afterReturning", method);
                                for (AspectInfo aspectInfo : afterReturnings) {
                                    aspectInfo.adviceMethod.invoke(aspectInfo.aspectBean,joinPoint);
                                }
                                return ret;
                            }catch(Exception e){
                                // TODO  异常增强
                                throw e;
                            }finally {
                                // TODO  后置增强
                            }

                        });
                proxyBeanMap.put(name,proxyBean);
            }
        }
        System.out.println("-------------- 代理对象 ---------------");
        proxyBeanMap.forEach((name,proxyBean)->System.out.println(name + " : " + proxyBean));

        // 使用代理对象替换容器中的bean
        proxyBeanMap.forEach((name,proxyBean)->{
            singletonObjects.put(name,proxyBean);
        });

    }

    // 根据类型返回指定的切面
    public List<AspectInfo> getAdvice(String type, Method method){
        List<AspectInfo> list = aspectInfoList.stream().filter(aspectInfo -> {
            if (aspectInfo.type.equals(type) == false) {
                return false;
            }
            String regExp = aspectInfo.pointcut.replaceAll(
                    "execution\\((.+)\\)", "$1");
            regExp = regExp.replaceAll("(\\.{2}|\\*)", ".*");
            if (method.toString().matches(regExp)) {
                return true;
            }
            return false;
        }).collect(Collectors.toList());
        return list;
    }

    public JoinPoint createJoinPoint(Method method, Object[] args, Object target){
        return new JoinPoint(){

            @Override
            public String toShortString() {
                return method.getName();
            }

            @Override
            public String toLongString() {
                return method.toString();
            }

            @Override
            public Object getThis() {
                return null;
            }

            @Override
            public Object getTarget() {
                return target;
            }

            @Override
            public Object[] getArgs() {
                return args;
            }

            @Override
            public Signature getSignature() {
                return null;
            }

            @Override
            public SourceLocation getSourceLocation() {
                return null;
            }

            @Override
            public String getKind() {
                return null;
            }

            @Override
            public StaticPart getStaticPart() {
                return null;
            }
        };
    }

    public static void main(String[] args) throws UnsupportedEncodingException, ClassNotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException {
        AopApplicationContext cxt = new AopApplicationContext(AopConfig.class);
        System.out.println("************************************************");
        IUserBiz userBiz = cxt.getBean(UserBiz.class);
        System.out.println("##############findByObject---------------");
        userBiz.findByObject(null);
        System.out.println("##############getUserMapper---------------");
        userBiz.login("root","123");
    }
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class AspectInfo{

    String type;
    Object aspectBean;
    Method adviceMethod;
    String pointcut;

}
